#include "ymodem.h"
#include "iap.h"
#include <string.h>
static uint16_t Cal_CRC16(const uint8_t* data, uint32_t size);
static void Int2Str(uint8_t* str, int32_t intnum);
static uint32_t Str2Int(uint8_t *inputstr, int32_t *intnum);
uint16_t packet_size;
uint16_t data_size;
uint16_t crc16;
uint8_t data = 0;
/* 0 for end; -1 for timeout or error; 1 for abort */
int8_t packet_parse(uint8_t *buf, int32_t *len, uint32_t timeout)
{
    if(uart_getchar(&data, timeout) != 0)
    {
        return -1;
    }
    switch(data)
    {
        case SOH:
        {
            packet_size = PACKET_SIZE_SOH;
            data_size = DATA_SIZE_SOH;
        }
        break;
        case STX:
        {
            packet_size = PACKET_SIZE_STX;
            data_size = DATA_SIZE_STX;
        }
        break;
        case EOT:
        {
            *len = 0;
            return 0;
        }
        break;
        case CA:
        {
            if(uart_getchar(&data, timeout) == 0 && data == CA)
            {
                *len = -1;
                return 0;
            }   
            else
            {
                return -1;
            }
        }
        break;
        case ABORT1:
        case ABORT2:
        {
            return 1;
        }
        break;
        default:
        {
            return -1;
        }
        break;
    }
    *buf = data;
    if(uart_getstream(&buf[1], packet_size - 1, 5000) != 0)
    {
        return -1;
    }
    if(buf[1] != ((buf[2] ^ 0xFF) & 0xFF))
    {
        return -1;
    }
    /* calculate and verify CRC */
    crc16 = buf[packet_size - 2] << 8 | buf[packet_size - 1];
    if(crc16 != Cal_CRC16(&buf[3], data_size))
    {
        return -1;
    }
    *len = data_size;
    return 0;
}

/* 0 for normal return; -1 for Image beyond size; -2 for FLASH error; -3 for */
int32_t ymodem_receive(uint8_t *buf)
{
    uint8_t packetData[PACKET_SIZE_STX];
    int32_t dataLen = 0;
    int32_t packetSN = 0;
    uint8_t fileName[FILE_NAME_LENGTH]; 
    uint8_t fileSize[FILE_SIZE_LENGTH];
    uint8_t *pData;
    uint8_t fileDone = 0;
    uint8_t transBegin = 0;
    uint8_t transDone = 0;
    int32_t size;
    int32_t i;
    uint8_t err = 0;
    uint32_t NbPage;
    uint32_t flashAddr = APPLICATION_ADDR;
    uint8_t *pBuf;
    
    /* 多文件传输 */
    for (transDone = 0, err = 0, transBegin = 0; ;)
    {
        /* 文件分段传输 */
        for (packetSN = 0, fileDone = 0, pBuf = buf; ;)
        {
            switch(packet_parse(packetData, &dataLen, 1000))
            {   
                case 0:
                {
                    /* clear err */
                    err = 0;    
                    switch(dataLen)
                    {
                        /* abort by sender */
                        case -1:
                        {
                            uart_putchar(ACK);
                            return 0;
                        }
                        break;
                        /* end of transmission */
                        case 0:
                        {
                            uart_putchar(ACK);
                            fileDone = 1;
                        }
                        break;
                        /* normal packet */
                        default:
                        {
                            /* packet SN not match */
                            if(packetData[1] != packetSN)
                            {
                                uart_putchar(NAK);
                            }
                            else
                            {
                                /* fileName packet */
                                if(packetSN == 0)
                                {
                                    /* filename packet is valid */
                                    if(packetData[PACKET_HEADER] != 0)
                                    {
                                        for(i = 0, pData = &packetData[PACKET_HEADER]; *pData != 0 && i < FILE_NAME_LENGTH - 1;)
                                        {
                                            fileName[i++] = *pData++;
                                        }
                                        fileName[i++] = '\0';
                                        for(i = 0, pData++; *pData != ' ' && i < FILE_SIZE_LENGTH - 1;)
                                        {
                                            fileSize[i++] = *pData++;
                                        }
                                        fileSize[i++] = '\0';
                                        Str2Int(fileSize, &size);
                                        /* Image size is greater than FLASH size */
                                        if(size > APPLICATION_FLASH_SIZE)
                                        {
                                            /* abort transmission */
                                            uart_putchar(CA);
                                            uart_putchar(CA);
                                            return -1;
                                        }
                                        NbPage = FLASH_PageMask(size);

                                        if(FLASH_Erase_pages(APPLICATION_PAGE_START, NbPage) == 0)
                                        {
                                            uart_putchar(ACK);
                                            uart_putchar(CRC16);
                                        }
                                    }
                                    /* filename packet is empty, end transmission */
                                    else
                                    {
                                        uart_putchar(ACK);
                                        fileDone = 1;
                                        transDone = 1;
                                        break;
                                    }
                                }
                                /* data packet */
                                else
                                {
                                    memcpy(buf, &packetData[PACKET_HEADER], dataLen);
                                    pBuf = buf;
                                    for(i = 0; i < (dataLen / 8); i++)
                                    {
                                        FLASH_Program_8bytes(flashAddr, *(uint64_t *)pBuf);
                                        if(*(uint64_t *)flashAddr != *(uint64_t *)pBuf)
                                        {
                                            /* End transmission */
                                            uart_putchar(CA);
                                            uart_putchar(CA);
                                            return -2;
                                        }
                                        pBuf += 8;
                                        flashAddr += 8;
                                    }
                                    uart_putchar(ACK);
                                }
                                packetSN++;
                                transBegin = 1;
                            }
                        }
                        break;
                    }
                    
                }
                break;
                /* abort by sender */
                case 1:
                {
                    uart_putchar(CA);
                    uart_putchar(CA);
                    return -3;
                }
                break;
                default:
                {
                    if (transBegin != 0)
                    {
                        err++;
                    }
                    if (err > MAX_ERROR_COUNT)
                    {
                        uart_putchar(CA);
                        uart_putchar(CA);
                        return 0;
                    }
                    uart_putchar(CRC16);
                    break;
                }
                break;
            }
            if(fileDone != 0)
            {
                break;
            }
        }
        if(transDone != 0)
        {
            break;
        }
    }
    return size;
}

/**
  * @brief  Update CRC16 for input byte
  * @param  CRC input value 
  * @param  input byte
   * @retval None
  */
static uint16_t UpdateCRC16(uint16_t crcIn, uint8_t byte)
{
    uint32_t crc = crcIn;
    uint32_t in = byte | 0x100;
    do
    {
        crc <<= 1;
        in <<= 1;
        if(in & 0x100)
        {
            ++crc;
        }
        if(crc & 0x10000)
        {
            crc ^= 0x1021;
        }
    }while(!(in & 0x10000));

    return (crc & 0xFFFFU);
}


/**
  * @brief  Cal CRC16 for YModem Packet
  * @param  data
  * @param  length
   * @retval None
  */
static uint16_t Cal_CRC16(const uint8_t* data, uint32_t size)
{
	uint32_t crc = 0;
	const uint8_t *dataEnd = data + size;
	while(data < dataEnd)
    {
		crc = UpdateCRC16(crc, *data++);	
	}
	
	crc = UpdateCRC16(crc, 0);
	crc = UpdateCRC16(crc, 0);
	return (crc & 0xFFFFU);
}

/**
  * @brief  Convert an Integer to a string
  * @param  str: The string
  * @param  intnum: The intger to be converted
  * @retval None
  */
static void Int2Str(uint8_t* str, int32_t intnum)
{
  uint32_t i, Div = 1000000000, j = 0, Status = 0;

  for (i = 0; i < 10; i++)
  {
    str[j++] = (intnum / Div) + 48;

    intnum = intnum % Div;
    Div /= 10;
    if ((str[j-1] == '0') & (Status == 0))
    {
      j = 0;
    }
    else
    {
      Status++;
    }
  }
}

/**
  * @brief  Convert a string to an integer
  * @param  inputstr: The string to be converted
  * @param  intnum: The intger value
  * @retval 1: Correct
  *         0: Error
  */
static uint32_t Str2Int(uint8_t *inputstr, int32_t *intnum)
{
  uint32_t i = 0, res = 0;
  uint32_t val = 0;

  if (inputstr[0] == '0' && (inputstr[1] == 'x' || inputstr[1] == 'X'))
  {
    if (inputstr[2] == '\0')
    {
      return 0;
    }
    for (i = 2; i < 11; i++)
    {
      if (inputstr[i] == '\0')
      {
        *intnum = val;
        /* return 1; */
        res = 1;
        break;
      }
      if (ISVALIDHEX(inputstr[i]))
      {
        val = (val << 4) + CONVERTHEX(inputstr[i]);
      }
      else
      {
        /* return 0, Invalid input */
        res = 0;
        break;
      }
    }
    /* over 8 digit hex --invalid */
    if (i >= 11)
    {
      res = 0;
    }
  }
  else /* max 10-digit decimal input */
  {
    for (i = 0;i < 11;i++)
    {
      if (inputstr[i] == '\0')
      {
        *intnum = val;
        /* return 1 */
        res = 1;
        break;
      }
      else if ((inputstr[i] == 'k' || inputstr[i] == 'K') && (i > 0))
      {
        val = val << 10;
        *intnum = val;
        res = 1;
        break;
      }
      else if ((inputstr[i] == 'm' || inputstr[i] == 'M') && (i > 0))
      {
        val = val << 20;
        *intnum = val;
        res = 1;
        break;
      }
      else if (ISVALIDDEC(inputstr[i]))
      {
        val = val * 10 + CONVERTDEC(inputstr[i]);
      }
      else
      {
        /* return 0, Invalid input */
        res = 0;
        break;
      }
    }
    /* Over 10 digit decimal --invalid */
    if (i >= 11)
    {
      res = 0;
    }
  }

  return res;
}

